import numpy as np
import torch
import sympy as syp


class torch_Tensor_class:
    """
    Tensor部分方法的使用示例
    """
    """
        torch.tensor(
            data, #数据，可以是list，numpy
            dtype=None, #数据类型，默认与data的一致
            device=None, #所在设备，cuda/cpu
            requires_grad=False, #是否需要梯度
            pin_memory=False #是否存在于锁页内存，会更快
        )       
    """

    def __init__(self):
        pass

    def Tensor_Create_basic(self):
        """
        创建Tensor
        :return:
        """

        ta = torch.Tensor([[1, 2, 3], [4, 5, 6]])

        """通过输入尺寸size创建零张量"""
        t0 = torch.zeros((3, 3))
        t0_stride = torch.zeros((1024, 1024), layout=torch.strided)
        """torch只有coo类型的稀疏矩阵"""
        t0_sparse = torch.zeros((1024, 1024), layout=torch.sparse_coo)
        """输入张量来创建一个同大小的零张量"""
        t0_like = torch.zeros_like(t0)
        """简单整数"""
        tl = torch.arange(1, 6)

    def Tensor_Create_more(self):
        """
        """
        """
        一维等差张量
        torch.linspace(start, end, steps=100)
        torch.linspace(
				start,
				end,
				steps=100,
				out=None,
				dtype=None,
				layout=torch.strided,
				device=None,
				requires_grad=False)
		"""
        # ---------------------------torch.linspace--------------------------------------------#
        t_linspace = torch.linspace(0, 100, 100)

        """
        一维指数等差张量
        torch.logspace(start, end, steps=100, base=10)   # base-指数的底

        torch.logspace(start,
				end,
				steps=100,
				base=10.0, #底，默认为10
				out=None,
				dtype=None,
				layout=torch.strided,
				device=None,
				requires_grad=False)
        """
        # ---------------------------torch.logspace--------------------------------------------#
        t_logspace = torch.logspace(0, 100, 100, base=2)

        """
        创建正态分布张量
        torch.normal(mean, std, out=None)  # mean-平均值; # std-标准差

        torch.normal(mean,
			std,
			out=None)
		返回一个张量，包含从给定参数means,std的离散正态分布中抽取随机数。 
		# means是一个张量，包含每个输出元素相关的正态分布的均值。 
		# std是一个张量，包含每个输出元素相关的正态分布的标准差。 
		均值和标准差的形状不须匹配，但每个张量的元素个数须相同。
		"""
        # 这个比较复杂，目前还不会

        """
        torch.rand(*size,
            out=None,
            dtype=None,
            layout=torch.strided,
            device=None,
            requires_grad=False)
        返回的范围是(0,1)
        
        torch.randn(*size: int,
            generator: Generator | None,
            names: Sequence[str | ellipsis | None] | None,
            out: Tensor | None = None,
            dtype: dtype | None = None,
            layout: layout = strided,
            device: device | str | None = None,
            requires_grad: bool = False)
        返回符合正态分布的随机数
        
        torch.randint(
			low=0,
			high, 
			size,
			out=None,
			dtype=None,
			layout=torch.strided,
			device=None,
			requires_grad=False
			)
			第一个参数定义了整数上限值
			"""
        # -------------------------------torch.rand----------------------------------------#
        t_rand = torch.rand((8, 4))
        t_randn = torch.randn((3, 2))
        t_randint = torch.randint(10, (5, 4))

        pass

    def Tensor_Edit_basic(self):
        """
        因为在Pycharm中可以看到函数的定义方式，所以这里不再添加函数的定义
        张量拼接




        :return:
        """
        t0 = torch.tensor([[1, 2], [3, 4], [5, 6]])
        t1 = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        t2 = torch.tensor([[11, 22, 33], [44, 55, 66], [77, 88, 99]])
        """
        # ----------------------------------torch.cat-------------------------------------#
        --将张量按维度dim进行拼接，不创建新的维度
        torch.cat(tensors, dim)  # tensors-张量序列; # dim-要拼接的维度:0-纵向拼接,1-横向拼接
        """
        t_cat1 = torch.cat([t1, t2], 0)
        t_cat2 = torch.cat([t1, t2], 1)

        """
        # ----------------------------------torch.stack-------------------------------------#
        --新创建的维度dim上进行拼接，创建新的维度
        torch.stack(tensors, dim)  # tensors-张量序列; # dim-要拼接的维度:0-纵向拼接,1-横向拼接

        """
        t_stack1 = torch.stack([t1, t2], 0)  # 索引方式: 深度-行-列
        t_stack2 = torch.stack([t1, t2], 1)  # 索引方式: 行-深度-列
        t_stack3 = torch.stack([t1, t2], 2)  # 索引方式: 列-行-深度
        print(t_stack1)
        """
        tensor([
        [[ 1,  2,  3],
         [ 4,  5,  6],
         [ 7,  8,  9]],
        [[11, 22, 33],
         [44, 55, 66],
         [77, 88, 99]]
         ])
         """
        print(t_stack2)
        """
        tensor([
        [[ 1,  2,  3],
         [11, 22, 33]],
        [[ 4,  5,  6],
         [44, 55, 66]],
        [[ 7,  8,  9],
         [77, 88, 99]]
         ])
         """
        print(t_stack3)
        """
        tensor([
        [[ 1, 11],
         [ 2, 22],
         [ 3, 33]],
        [[ 4, 44],
         [ 5, 55],
         [ 6, 66]],
        [[ 7, 77],
         [ 8, 88],
         [ 9, 99]]])
         """

        """
        # ----------------------------torch.chunk-------------------------------------------#
        张量切分
        torch.chunk(input, chunks, dim=0)  # chunks-要切分的份数; # dim-切分的维度:0-行切分,1-列切分,2-深度切分
        torch.split(tensor, split_size_or_sections,	dim=0) #split_size_or_sections:int-表示每一份的长度 :list-按照列表切分
        """
        # t0 3行2列
        t0 = torch.tensor([[1, 2], [3, 4], [5, 6]])
        t_chunk1 = torch.chunk(t0, 3, dim=0)
        t_chunk2 = torch.chunk(t0, 2, dim=1)

        # ----------------------------torch.split-------------------------------------------#
        t_split1 = torch.split(t0, 1, dim=0)
        t_split2 = torch.split(t0, 1, dim=1)


        """        
        # ----------------------------torch.nn.Unfold------------------------------------------#
        torch.nn.Unfold() 
        张量滑窗切分
        torch.nn.Unfold(input, kernel_size, dilation=1, padding=0, stride=1)
            # input-tensor数据，四维， Batchsize, channel, height, width
            # kernel_size-核大小，决定输出tensor的数目
            # dilation-输出形式是否有间隔
            # padding-边缘大小
            # stride-步长
        该函数能够实现对张量的滑窗切分，若原始张量维度为[N,C,H,W]，则经过unfold后得到[N,C*k*k,H*W]
        其中，k是卷积核的大小
        --unfold函数的输入数据是四维，但输出是三维的
        --假设输入数据是[B, C, H, W], 那么输出数据是 [B, C* kH * kW, L],
        --其中kH是核的高，kW是核宽。 L则是这个高kH宽kW的核能在H*W区域按照指定stride滑动的次数。

        """

        t00 = torch.rand((10, 8, 8, 8))
        k_unfold = torch.nn.Unfold(kernel_size=(3, 3), padding=1, stride=1)
        k_unfold(t00)
        # todo: 以后补充这个函数的用法

        """
        #----------------------------torch.index_select------------------------------------------
        张量索引合并
        torch.index_select(input, dim, index)
            # input-输入的张量
            # dim-索引的维度:0-行索引,1-列索引,2-深度索引
            # index-索引列表:Tensor
        """
        t00 = torch.randint(9, (10, 8))
        index = torch.tensor([0, 2, 4, 6])
        # 返回0行、2行、4行、6行
        t_select_index = torch.index_select(t00, dim=0, index=index)


        """
        # ----------------------------torch.masked_select------------------------------------------#
        张量掩码过滤
        torch.masked_select(input, mask) # mask-布尔类型的Tensor
        torch.masked_select 返回的是一维的
        """
        t00 = torch.randint(9, size=(4, 5))
        mask = t00.ge(5)  # ge()返回大于等于n为True的布尔类型
        # 只返回大于5的部分，其他部分没了
        t_select_mask = torch.masked_select(t00, mask)
        """
        ---------------------------torch.transpose()------------------------------------------#
        张量变换
        torch.transpose(input, dim0, dim1)
        """
        t00 = torch.randint(9, size=(4, 5))
        # 转置：
        t00_T = torch.t(t00)

        # ----------------------------reshape
        # 按照内存中的顺序进行安排
        t00 = torch.randint(9, size=(4, 5))
        t_reshape = t00.reshape((5, 4))

        pass

    def Tensor_Calc_autograd(self):
        """
        计算图(Computation Graph)是现代深度学习框架如PyTorch和TensorFlow等的核心，
        其为高效自动求导算法——反向传播(Back Propogation)提供了理论支持，了解计算图在实际写程序过程中会有极大的帮助。
        求导是件辛苦事儿，所以自动求导基本上是各种深度学习框架的基本功能和最重要的功能之一
        :return:
        """
        """
        1. 所有的tensor都有.requires_grad属性，都可以设置成自动求导。具体方法就是在定义tensor的时候，让这个属性为True
        2. 后面由该tensor经过运算得到的其他tensor，就都有requires_grad=True属性了。
        """

        # ----------------定义计算图----------------------------------------#
        n = 2
        m = 3
        """自动求导的张量必须是浮点数格式"""
        t = torch.randn(n, m, requires_grad=True, dtype=torch.float64)

        """-- 可以通过requires_grad_() 改变 requires_grad 这个属性"""
        # t.requires_grad_(False)
        y = t + 1
        z = 2 * y ** 2
        J = torch.mean(z)
        """
        J是标量，torch.mean()返回的是所有元素的平均值
        求导，只能是【标量】对标量，或者【标量】对向量/矩阵求导！
        
        只能J对t、y、z求导，而z则不能对t求导
        """

        # ---------------J对t求导-------------------------------------------#
        J.backward()  # 结果保存在 t 中
        print("自动求导结果:\n{0}".format(t.grad))

        # ---------------理论值--------------------------------------------#
        St = syp.symbols('t')
        Sy = St + 1
        Sz = 2 * Sy ** 2
        SJ = 1 / (n * m) * syp.diff(Sz, St)
        tt = t.detach().numpy().tolist()  # 需要求导的张量不能直接转换为np数组，需要先detach
        dJ_t = np.zeros((n, m))
        for i in range(0, n):
            for j in range(0, m):
                dJ_t[i][j] = SJ.subs(St, tt[i][j])
        print("理论结果: \n{0}".format(dJ_t))
        pass
